04. 重置 commit

重置与还原

初看, 重置(reset) 似乎和 还原(revert) 相似,但它们实际上差别很大。还原会创建一个新的 commit,并还原或撤消之前的 commit。但是重置会清除 commit!

⚠️ 重置很危险 ⚠️

一定要谨慎使用 git 的重置功能。这是少数几个可以从仓库中清除 commit 的命令。如果某个 commit 不再存在于仓库中,它所包含的内容也会消失。

为了减轻你的压力,澄清下,git 会在完全清除任何内容之前,持续跟踪大约 30 天。要调用这些内容,你需要使用 git reflog 命令。请参阅以下链接以了解详情:

相关 commit 引用

你已经知道可以使用 SHA、标签、分支和特殊的 HEAD 指针引用 commit。有时候这些并不足够,你可能需要引用相对于另一个 commit 的 commit。例如,有时候你需要告诉 git 调用当前 commit 的前一个 commit,或者是前两个 commit。我们可以使用特殊的“祖先引用”字符来告诉 git 这些相对引用。这些字符为:

  • ^ – 表示父 commit
  • ~ – 表示第一个父 commit

我们可以通过以下方式引用之前的 commit:

  • 父 commit – 以下内容表示当前 commit 的父 commit
  • HEAD^
  • HEAD~
  • HEAD~1
  • 祖父 commit – 以下内容表示当前 commit 的祖父 commit
  • HEAD^^
  • HEAD~2
  • 曾祖父 commit – 以下内容表示当前 commit 的曾祖父 commit
  • HEAD^^^
  • HEAD~3

^ ~ 的区别主要体现在通过合并而创建的 commit 中。合并 commit 具有两个父级。对于合并 commit, ^ 引用用来表示第一个父 commit,而 ^2 表示第二个父 commit。第一个父 commit 是当你运行 git merge 时所处的分支,而第二个父 commit 是被合并的分支。

我们来看一个示例,这样更好理解。这是我的 git log 当前的显示结果:

* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
*   796ddb0 Merge branch 'heading-update'
|\  
| * 4c9749e (heading-update) Set page heading to "Crusade"
* | 0c5975a Set page heading to "Quest"
|/  
*   1a56a81 Merge branch 'sidebar'
|\  
| * f69811c (sidebar) Update sidebar with favorite movie
| * e6c65a6 Add new sidebar content
* | e014d91 (footer) Add links to social media
* | 209752a Improve site heading for SEO
* | 3772ab1 Set background color for page
|/  
* 5bfe5e7 Add starting HTML structure
* 6fa5f34 Add .gitignore file
* a879849 Add header to blog
* 94de470 Initial commit

我们来看看如何引用一些之前的 commit。因为 HEAD 指向 9ec05ca commit:

  • HEAD^ db7e87a commit
  • HEAD~1 同样是 db7e87a commit
  • HEAD^^ 796ddb0 commit
  • HEAD~2 同样是 796ddb0 commit
  • HEAD^^^ 0c5975a commit
  • HEAD~3 同样是 0c5975a commit
  • HEAD^^^2 4c9749e commit(这是曾祖父的 ( HEAD^^ ) 第二个父 commit ( ^2 ))

哪一个 commit?

请使用此仓库回答以下练习问题:

* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
*   796ddb0 Merge branch 'heading-update'
|\  
| * 4c9749e (heading-update) Set page heading to "Crusade"
* | 0c5975a Set page heading to "Quest"
|/  
*   1a56a81 Merge branch 'sidebar'
|\  
| * f69811c (sidebar) Update sidebar with favorite movie
| * e6c65a6 Add new sidebar content
* | e014d91 (footer) Add links to social media
* | 209752a Improve site heading for SEO
* | 3772ab1 Set background color for page
|/  
* 5bfe5e7 Add starting HTML structure
* 6fa5f34 Add .gitignore file
* a879849 Add header to blog
* 94de470 Initial commit

哪一个 commit?

HEAD~6 引用的是哪一个 commit?

SOLUTION: 209752a

QUESTION:

上道题答的不错,再试试这道题吧!对于相同的仓库, HEAD~4^2 引用的是哪个 commit?

SOLUTION:

NOTE: The solutions are expressed in RegEx pattern. Udacity uses these patterns to check the given answer

git reset 命令

git reset 命令用来重置(清除)commit:

$ git reset <reference-to-commit>

可以用来:

  • 将 HEAD 和当前分支指针移到目标 commit
  • 清除 commit
  • 将 commit 的更改移到暂存区
  • 取消暂存 commit 的更改

git reset 的选项

git 根据所使用选项来判断是清除、暂存之前 commit 的更改,还是取消暂存之前 commit 的更改。这些选项包括:

  • --mixed
  • --soft
  • --hard

通过动画的形式更容易理解这些选项的工作方式。

Nd016 WebND Ud123 Gitcourse BETAMOJITO L6 17 Soft Vs Medium Vs Hard Walkthrough

💡 备份分支 💡

注意,使用 git reset 命令将清除当前分支上的 commit。因此,如果你想跟着操作接下来出现的所有重置操作,需要在当前 commit 上创建一个分支,以便用作备份。

在进行任何重置操作之前,我通常会在最近的 commit 上创建一个 backup 分支,因此如果出现错误,我可以返回这些 commit:

$ git branch backup

reset 的 --mixed 选项

我们来看看每个选项。

* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
* 796ddb0 Merge branch 'heading-update'

使用上述示例仓库,其中 HEAD 指向 9ec05ca 上的 master ,运行 git reset --mixed HEAD^ 会把 commit 9ec05ca 中做出的更改移至工作目录中。

_终端显示了使用 `--mixed` 选项进行重置后的结果。更改未暂存。_

终端显示了使用 --mixed 选项进行重置后的结果。更改未暂存。

💡 回到正常状况💡

如果你在重置任何内容前创建了 backup 分支,那么你可以轻松地让 master 分支指向 backup 分支所指向的同一 commit。你只需:

  1. 从工作目录中删除未 commit 的更改
  • backup 合并到 master (这将导致快进合并并使 master 向上移到和 backup 一样的点)
$ git checkout -- index.html
$ git merge backup

reset 的 --soft 选项

我们使用相同的几个 commit 并看看 --soft 选项的工作方式:

* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
* 796ddb0 Merge branch 'heading-update'

运行 git reset --soft HEAD^ 会把 commit 9ec05ca 中做出的更改直接移至暂存区。

_终端显示了使用 `--soft` 选项进行重置后的结果。更改被移到暂存区。_

终端显示了使用 --soft 选项进行重置后的结果。更改被移到暂存区。

reset 的 --hard 选项

最后再看看 --hard 选项(当然并非最不重要选项):

* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
* 796ddb0 Merge branch 'heading-update'

运行 git reset --hard HEAD^ 将清除 commit 9ec05ca 中做出的更改。

_终端显示了使用 `--hard` 选项进行重置后的结果。更改被清除。_

终端显示了使用 --hard 选项进行重置后的结果。更改被清除。

现在该你了!

对于以下仓库:

* e014d91 (HEAD -> master, footer) Add links to social media
* 209752a Improve site heading for SEO
* 3772ab1 Set background color for page
* 5bfe5e7 Add starting HTML structure
* 6fa5f34 Add .gitignore file
* a879849 Add header to blog
* 94de470 Initial commit

内容在哪?

如果运行 git reset --hard HEAD~3 3772ab1 commit 中的更改会怎样?更改会被移入暂存区?工作目录?还是完全被清除呢?

SOLUTION: 被清除

内容在哪 2?

如果运行 git reset --soft HEAD^^ 209752a commit 中的更改将怎样?更改会进入暂存区、工作目录或完全被清除吗?

SOLUTION: 暂存区

reset 小结

总结下, git reset 命令被用来清除 commit:

$ git reset <reference-to-commit>

它可以用来:

  • 将 HEAD 和当前分支指针移到引用的 commit
  • 使用 --hard 选项清除 commit
  • 使用 --soft 选项将 commit 的更改移至暂存区
  • 使用 --mixed 选项取消暂存已被 commit 的更改

我们通常会用到祖先引用来指代之前的 commit。祖先引用包含:

  • ^ – 表示父 commit
  • ~ – 表示第一个父 commit

进一步研究